﻿#region MIT license
/*
 Copyright (c) 2009-2010 Joxon Productions <joxonproductions@gmail.com>

 Permission is hereby granted, free of charge, to any person
 obtaining a copy of this software and associated documentation
 files (the "Software"), to deal in the Software without
 restriction, including without limitation the rights to use,
 copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the
 Software is furnished to do so, subject to the following
 conditions:

 The above copyright notice and this permission notice shall be
 included in all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.
*/
#endregion
        
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace JAsyncServerLib
{
    public class JServer
    {
        public const int MAX_CLIENTS = 1000;

        #region Private Variables
        protected Socket m_Socket;
        protected int m_Clients = 0;
        protected int m_Port = 0;
        protected JServerThread m_AsyncRunnable;
        protected Thread m_AsyncRunnableThread;

        #endregion
        #region Constructor
        public JServer()
        {
            ClientManager = new JClientMgr(this);
            m_AsyncRunnable = new JServerThread(ClientManager);
            m_AsyncRunnableThread = new Thread(new ThreadStart(m_AsyncRunnable.Run));
        }
        #endregion

        public void Start()
        {
            m_AsyncRunnableThread.Start();
        }

        public void Stop(bool async)
        {
            ClientManager.StopPending = true;

            if (!async)
            {
                while (m_AsyncRunnable.Running)
                {
                    Thread.Sleep(1); // Wait for it to end
                }
            }
        }

        public JClientMgr ClientManager
        {
            get;
            internal set;
        }

        public Socket RawSocket
        {
            get;
            internal set;
        }

        public void ListenForData(JClient client)
        {
            try
            {
                JClientObject theSocPkt = new JClientObject();
                theSocPkt.CurrentSocket = client.RawRocket;
                theSocPkt.Client = client;
                theSocPkt.ListenSocket = m_Socket;
                theSocPkt.Server = this;

                // Start receiving any data written by the connected client
                // asynchronously
                theSocPkt.CurrentSocket.BeginReceive(theSocPkt.DataBuffer, 0,
                                   theSocPkt.DataBuffer.Length,
                                   SocketFlags.None,
                                   new AsyncCallback(client.SocketOnRead),
                                  theSocPkt);
            }
            catch (SocketException se)
            {
                CallOnException(se);
            }
        }

        public void Listen(int port)
        {
            if (m_Socket != null)
            {
                m_Socket.Shutdown(SocketShutdown.Both);
                m_Socket.Close();
                m_Socket = null;
            }

            m_Port = port;
            m_Socket = new Socket(AddressFamily.InterNetwork,
                                          SocketType.Stream,
                                          ProtocolType.Tcp);

            IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, m_Port);
            m_Socket.Bind(ipLocal);
            m_Socket.Listen(MAX_CLIENTS); // TODO Make configurable 
            m_Socket.BeginAccept(new AsyncCallback(OnClientSocketConnect), null);
        }

        internal virtual void OnClientSocketConnect(IAsyncResult asyn)
        {
                       Socket newSocket;
            try
            {
                // Here we complete/end the BeginAccept() asynchronous call
                // by calling EndAccept() - which returns the reference to
                // a new Socket object
                newSocket = m_Socket.EndAccept(asyn);

                // increment the client count
                m_Clients++;

                // Since the main Socket is now free, it can go back and wait for
                // other clients who are attempting to connect
                m_Socket.BeginAccept(new AsyncCallback(OnClientSocketConnect), this);

                // Let the worker Socket do the further processing for the 
                // just connected client
                //AsyncSocket wSocket = new AsyncSocket(newSocket);

                //wSocket.OnAccept(this);
                JClient gSession = new JClient(); /// newSocket, this
                gSession.Init(newSocket, this);
                
                lock (ClientManager.ObjectGuid)
                {
                    ClientManager.Add(gSession);
                }

                // Check if the event is assigned
                if (OnConnect != null)
                {
                    OnConnect(gSession);
                }

                // Start listening for data now
                ListenForData(gSession);



            }
            catch (ObjectDisposedException exx)
            {
                System.Diagnostics.Debugger.Log(0, "1", "\n socketOnClientConnection: Socket has been closed\n");
                CallOnException(exx);
            }
            catch (SocketException se)
            {
                CallOnException(se);
            }
        }

        public void SendData(JClient wsocket, byte[] bytes)
        {
            wsocket.Send(bytes);
        }


        #region Events
        
        public event OnReceiveDelegate OnReceive;
        public event OnDisconnectDelegate OnDisconnect;
        public event OnErrorDelegate OnError;
        public event OnExceptionDelegate OnException;
        public event OnExceptionWithMessageDelegate OnExceptionWithMessage;
        public event OnUpdateDelegate OnUpdate;
        public event OnConnectDelegate OnConnect;

        internal void CallOnError(string Error, Object optionalObj)
        {
            if (OnError != null)
            {
                OnError(Error, optionalObj);
            }
        }
        internal void CallOnConnect(JClient client)
        {
            if (OnConnect != null)
            {
                OnConnect(client);
            }
        }

        internal void CallOnDisconnect(JClient client)
        {
            m_Clients--; // Remove one client from the counter
            ClientManager.Remove(client); // Remove the session, it is not 'destroyed' yet, just, gone

            if (OnDisconnect != null)
            {
                OnDisconnect(client);
            }

        }
        internal void CallOnReceive(JClient client, byte[] dataReceived)
        {
            if (OnReceive != null)
            {
                OnReceive(client, dataReceived);
            }
        }

        internal void CallOnException(Exception ex)
        {
            if (OnException != null)
            {
                OnException(ex);
                return;
            }
            throw ex;
        }
        internal void CallOnException(Exception ex, string Message)
        {
            if (OnExceptionWithMessage != null)
            {
                OnExceptionWithMessage(ex, Message);
                return;
            }

            throw ex;
        }
        internal void CallOnUpdate()
        {
            if (OnUpdate != null)
            {
                OnUpdate();
                return;
            }
        }
        #endregion


        public delegate void OnConnectDelegate(JClient session);
        public delegate void OnReceiveDelegate(JClient session, byte[] dataReceived);
        public delegate void OnDisconnectDelegate(JClient session);
        public delegate void OnErrorDelegate(string Error, Object optionalObj);
        public delegate void OnExceptionWithMessageDelegate(Exception exception, string OptionalMessage);
        public delegate void OnExceptionDelegate(Exception exception);
        public delegate void OnUpdateDelegate();


    }
    public class JServer<T> : JServer where T : JClient, new()
    {

        internal override void OnClientSocketConnect(IAsyncResult asyn)
        {
            Socket newSocket;
            try
            {
                // Here we complete/end the BeginAccept() asynchronous call
                // by calling EndAccept() - which returns the reference to
                // a new Socket object
                newSocket = m_Socket.EndAccept(asyn);

                // increment the client count
                m_Clients++;

                // Since the main Socket is now free, it can go back and wait for
                // other clients who are attempting to connect
                m_Socket.BeginAccept(new AsyncCallback(OnClientSocketConnect), this);

                // Let the worker Socket do the further processing for the 
                // just connected client
                //AsyncSocket wSocket = new AsyncSocket(newSocket);

                //wSocket.OnAccept(this);
                JClient gSession = new T(); /// newSocket, this
                gSession.Init(newSocket, this);
                
                lock (ClientManager.ObjectGuid)
                {
                    ClientManager.Add(gSession);
                }

                // Check if the event is assigned
                CallOnConnect(gSession);

                // Start listening for data now
                ListenForData(gSession);
            }
            catch (ObjectDisposedException exx)
            {
                System.Diagnostics.Debugger.Log(0, "1", "\n socketOnClientConnection: Socket has been closed\n");
                CallOnException(exx);
            }
            catch (SocketException se)
            {
                CallOnException(se);
            }
        }
    }
}
